package com.hzw.saas.web.shell.service;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

import com.hzw.saas.api.storage.bo.InxChunkBO;
import com.hzw.saas.web.shell.service.bo.SftpFileBO;
import com.hzw.saas.web.shell.service.bo.SshConnBO;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.crypto.digest.MD5;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import lombok.extern.slf4j.Slf4j;

/**
 * @author zzl
 * @since 06/04/2021
 */
@RunWith(SpringRunner.class)
@TestPropertySource("classpath:sshserver.properties")
@SpringBootTest
@Slf4j
public class SftpFileServiceImplTestCase {

    @Value("${sshserver.host}")
    String host;

    @Value("${sshserver.username}")
    String username;

    @Value("${sshserver.password}")
    String password;

    @Autowired
    private ISftpFileService sftpFileService;

    private SshConnBO sshConnBO;

    @Before
    public void init() {
        // 清理本地temp目录
        File file = new File(System.getProperty("user.dir") + "/temp");
        if (file.exists()){
            file.delete();
        }
        file.mkdir();
    }
    @After
    public void end() {
        File file = new File(System.getProperty("user.dir") + "/temp");
        if (file.exists()){
            file.delete();
        }
        // 清理本地temp目录
    }
    @Test
    public void test_sftp_dir01() {
        // step: 连接sftp
        sshConnBO = new SshConnBO();
        sshConnBO.setHost(host);
        sshConnBO.setPort(22);
        sshConnBO.setUsername(username);
        sshConnBO.setPassword(password);
        String yyyyMMdd = DateUtil.format(new Date(), "yyyyMMdd");
        String path = "/root/test"+"/"+yyyyMMdd;
        // step: 判断服务器目录/root/test/<timestamp> 是否存在
        boolean exist = sftpFileService.exist(sshConnBO, path);
        // assert: 目录/root/test/<timestamp>不存在
        log.debug("exist: {}", exist);
        Assert.assertTrue(exist+"",true);
        if (!exist){
            // step: 创建服务器目录/root/test/<timestamp>
            sftpFileService.mkdir(sshConnBO,path);
        }
        // step: 判断服务器目录/root/test/<timestamp> 是否存在
        boolean exist1 = sftpFileService.exist(sshConnBO, path);

        // assert: 目录/root/test/<timestamp>存在
        log.debug("exist: {}", exist1);
        Assert.assertTrue(exist1+"",true);


        // step: 创建服务器目录/root/test/<timestamp>/<timestamp2>
        sftpFileService.mkdir(sshConnBO,path+ "/"+DateUtil.format(new Date(),"yyyyMMdd"));
        boolean exist2 = sftpFileService.exist(sshConnBO, path+"/" + DateUtil.format(new Date(), "yyyyMMdd"));


        // assert: 目录/root/test/<timestamp>/<timestamp2>存在
        log.debug("exist: {}", exist2);
        Assert.assertTrue(exist2+"",true);

        // step: 删除服务器目录/root/test/<timestamp>
        sftpFileService.rm(sshConnBO,path);

        // assert: 目录/root/test/<timestamp>/<timestamp2>不存在
        boolean exist3 = sftpFileService.exist(sshConnBO, path+"/" + DateUtil.format(new Date(), "yyyyMMdd"));
        Assert.assertTrue(exist3+"",true);
        boolean exist4 = sftpFileService.exist(sshConnBO, path);

        // assert: 目录/root/test/<timestamp>不存在
        Assert.assertTrue(exist4+"",true);
        // step: 断开sftp连接
    }

    public File downloadFromS3(String url,String filePath,String name ){

        FileOutputStream fileOut = null;
        HttpURLConnection conn = null;
        InputStream inputStream = null;
        File file=new File(filePath);
        try {
            URL httpUrl = new URL(url);
            URLConnection urlConnection = httpUrl.openConnection();
            urlConnection.setDoInput(true);
            urlConnection.setDoOutput(true);
            urlConnection.connect();
            inputStream =  urlConnection.getInputStream();

            BufferedInputStream bis = new BufferedInputStream(inputStream);
            fileOut = new FileOutputStream(filePath+name);
            BufferedOutputStream bos = new BufferedOutputStream(fileOut);

            byte[] buf = new byte[4096];
            int length = bis.read(buf);
            //保存文件
            while(length != -1)
            {
                bos.write(buf, 0, length);
                length = bis.read(buf);
            }
//            conn.disconnect();
            bos.close();
            bis.close();

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return file;
    }


    public byte[] splitUrl(String url){

        String[] split = url.split("/");
        List<String> collect = Arrays.stream(split).collect(Collectors.toList());
        String s = collect.get(collect.size() - 1);
        return s.getBytes(StandardCharsets.UTF_8);

    }


    @Test
    public void test_sftp_file01() {

        sshConnBO = new SshConnBO();
        sshConnBO.setHost(host);
        sshConnBO.setPort(22);
        sshConnBO.setUsername(username);
        sshConnBO.setPassword(password);

        init();

        String localPath =  System.getProperty("user.dir")+"/temp";
        List<String> file1 = getFile(localPath);
        // 待测试的原始文件（S3地址）
        List<String> fileS3Urls = new ArrayList<String>() {
            {
                add("https://prod-saas-base.s3.cn-northwest-1.amazonaws.com.cn/manual/%E9%B8%BF%E4%B9%8B%E5%BE%AE%E4%BA%91%E5%B9%B3%E5%8F%B0%E4%BB%8B%E7%BB%8D.pdf");
                add("https://prod-saas-base.s3.cn-northwest-1.amazonaws.com.cn/expr/20210427/%E5%8A%A9%E5%8A%9B%E7%A7%91%E7%A0%94%C2%B7%E4%BA%91%E7%AB%AF%E8%A7%A3%E6%9E%90%E9%87%8F%E5%AD%90%E8%BE%93%E8%BF%90x.mp4");
                add("https://prod-saas-base.s3.cn-northwest-1.amazonaws.com.cn/prod/first/downloads/FIRST2021A0513.zip");
            }
        };
        // step: 从S3下载到本地目录temp/upload

        for (int i = 0;i< fileS3Urls.size();i++) {
            byte[] bytes = splitUrl(fileS3Urls.get(i));
            String s = new String(bytes, StandardCharsets.UTF_8);
            if (!file1.contains(s)) {
                downloadFromS3(fileS3Urls.get(i), localPath, "/" + s);
            }
        }
        // step: 计算temp/upload下每个文件的md5
//     List<SftpFileBO> list = sftpFileService.list(sshConnBO, localPath);
        List<String> localObj = getFile(localPath);

        if (CollectionUtil.isEmpty(localObj)){
            Assert.assertFalse("制定路径下没有文件",false);
        }


        // step: 连接sftp
        String remotePath = "/root/test";
        // step: 创建服务器目录/root/test/<timestamp>
        String timestamp = remotePath+ "/"+DateUtil.format(new Date(),"yyyyMMdd");
        if (!sftpFileService.exist(sshConnBO,timestamp)){
            sftpFileService.mkdir(sshConnBO, timestamp);
        }
        HashMap<String, byte[]> stringHashMap = new HashMap<>();
        localObj.forEach(s -> {
            File file = new File(localPath + "/" + s);
            BufferedInputStream inputStream = FileUtil.getInputStream(file);
            MD5 md5 = MD5.create();
            byte[] digest = md5.digest(file);
            stringHashMap.put(s, digest);
            InxChunkBO inxChunkBO = new InxChunkBO();
            inxChunkBO.setChunkNumber(1);
            inxChunkBO.setChunkSize(file.length());
            inxChunkBO.setCurrentChunkSize(1L);
            inxChunkBO.setFilename(s);
            if (!sftpFileService.exist(sshConnBO,timestamp+"/"+s)) {
                sftpFileService.transferChunkOnSftp(sshConnBO, timestamp, inxChunkBO, inputStream);
            }
        });
        // step: 获取服务器目录/root/test/<timestamp>下所有文件列表
        List<SftpFileBO> sftpFileBOS = sftpFileService.list(sshConnBO, timestamp);
        if (CollectionUtil.isEmpty(sftpFileBOS)){
            Assert.assertFalse("上传失败",false);
        }

        // assert: 服务器目录/root/test/<timestamp>下所有文件名与文件长度与temp/upload对应的文件一致
        HashMap<String, Long> remoteMap = new HashMap<>();
        HashMap<String, Long> localMap = new HashMap<>();
        HashMap<String, Long> differentLength = new HashMap<>();
        sftpFileBOS.forEach(sftpFileBO -> {
            if (sftpFileBO.getType() == 2) {
                remoteMap.put(sftpFileBO.getName(), sftpFileBO.getLength());
            }
        });
        localObj.forEach(s -> {

                    localMap.put(s,getFileSize(localPath+"/"+s));

        });
        ArrayList<String> strings1 = new ArrayList<>();

        localMap.forEach((s,l)->{
            remoteMap.forEach((s1,l1)->{
                if (s.equals(s1)){
                    if (!l.equals(l1)){
                        differentLength.put(s,l);
                    }
                    strings1.add(s);
                }
            });
        });
        //解决并发异常
        List<String> collect1 = sftpFileBOS.stream().map(SftpFileBO::getName).collect(Collectors.toList());
        collect1.removeAll(strings1);
        if (CollectionUtil.isNotEmpty(collect1)){
            Assert.assertFalse(localMap+"未上传成功",false);
        }


        if (!differentLength.isEmpty()){
            Assert.assertFalse(differentLength+"未上传成功,文件长度不一样",false);
        }

        // step: 将服务器目录/root/test/<timestamp>复制到/root/test/<timestamp2>
        String yyyyMMddhhMMss = DateUtil.format(new Date(), "yyyyMMddhhMMss");
        String timestamp2 =timestamp+yyyyMMddhhMMss;
        sftpFileService.cp(sshConnBO,Arrays.asList(timestamp),timestamp2);
        List<SftpFileBO> cp = sftpFileService.list(sshConnBO, timestamp2);

        HashMap<String, Long> cpAfter = new HashMap<>();
        HashMap<String, Long> cpBefore = new HashMap<>();
        HashMap<String, Long> differentLengthCp = new HashMap<>();
        sftpFileBOS.forEach(sftpFileBO -> {
            cpAfter.put(sftpFileBO.getName(),sftpFileBO.getLength());
        });
        cp.forEach(sftpFileBO -> {
            cpBefore.put(sftpFileBO.getName(),sftpFileBO.getLength());
        });
        //解决并发异常
        ArrayList<String> strings2 = new ArrayList<>();
        cpAfter.forEach((s,l)->{
            cpBefore.forEach((s1,l1)->{
                if (s.equals(s1)){
                    if (!l.equals(l1)){
                        differentLengthCp.put(s,l);
                    }
                    strings2.add(s);
                }
            });
        });

        List<String> collect2 = cp.stream().map(SftpFileBO::getName).collect(Collectors.toList());
        collect2.removeAll(strings2);
        if (CollectionUtil.isNotEmpty(collect2)){
            Assert.assertFalse(cpAfter+"未上传成功",false);

        }
        if (!differentLengthCp.isEmpty()){
            Assert.assertFalse(differentLengthCp+"未上传成功,文件长度不一样",false);
        }
        // step: 删除服务器目录/root/test/<timestamp2>
        sftpFileService.rm(sshConnBO,timestamp2);
        // step: 判断服务器目录/root/test/<timestamp2> 是否存在
        List<SftpFileBO> list1 = sftpFileService.list(sshConnBO, timestamp2);
        // assert: 服务器目录/root/test/<timestamp2>不存在
        if (CollectionUtil.isEmpty(list1)){
            Assert.assertTrue("删除成功",true);
        }
        // step: 从服务器目录/root/test/<timestamp>下载所有文件到本地目录temp/download
        //创建本地目录
        String localDown = localPath+"/test";

        File file2 = new File(localDown);
        if (!file2.exists()){
            file2.mkdir();
        }

        sftpFileBOS.forEach(sftpFileBO -> {
           if (sftpFileBO.getType() == 2) {

               File file = new File(localDown+"/"+sftpFileBO.getName());
               OutputStream ous = FileUtil.getOutputStream(file);
               sftpFileService.download(sshConnBO, timestamp+"/"+sftpFileBO.getName(), ous);
           }
       });
        // step: 计算本地目录temp/download下所有文件的md5
        List<String> file = getFile(localDown);
        HashMap<String, byte[]> localMd5 = new HashMap<>();
        file.forEach(s -> {
            File file3 = new File(localDown+"/"+s);
            MD5 md5 = MD5.create();
            byte[] digest = md5.digest(file3);
            localMd5.put(s,digest);
        });

        // assert: 本地目录temp/download下所有文件的md5与step2计算的md5一致
        ArrayList<String> strings = new ArrayList<>();
        stringHashMap.forEach((s, bytes) -> {
            localMd5.forEach((s1, bytes1) -> {
                if (s.equals(s1)){
                    if (!bytes.equals(bytes1)){
                        strings.add(s);
                    }
                }
            });
        });
        if(CollectionUtil.isEmpty(strings)){
            Assert.assertTrue("哈希值一样",true);
        }

        List<SftpFileBO> list2 = sftpFileService.list(sshConnBO, timestamp);
        // step: 删除服务器目录/root/test/<timestamp>下所有文件
        list2.forEach(sftpFileBO -> {
            sftpFileService.rm(sshConnBO,timestamp+"/"+sftpFileBO.getName());
        });
        // step: 获取服务器目录/root/test/<timestamp>下所有文件列表
        List<SftpFileBO> list3 = sftpFileService.list(sshConnBO, timestamp);

        // assert: 服务器目录/root/test/<timestamp>文件列表为空
        if (CollectionUtil.isEmpty(list3)){
            Assert.assertTrue("删除成功",true);
        }
        // step: 删除服务器目录/root/test/<timestamp>
        sftpFileService.rm(sshConnBO,timestamp);
        // step: 断开sftp连接
        sftpFileService.rm(sshConnBO,remotePath);
        end();
    }

        /**
     * 查找指定路径下的文件名
     * @param path
     */
    private  List<String>  getFile(String path) {
        // get file list where the path has
        File file = new File(path);
        // get the folder list
        File[] array = file.listFiles();
        ArrayList<String> strings = new ArrayList<>();
        for (int i = 0; i < array.length; i++) {
            if (array[i].isFile()) {
                strings.add(array[i].getName());
            } else if (array[i].isDirectory()) {
                getFile(array[i].getPath());
            }
        }
        return strings;
    }

    /**
     * 计算文件大小
     * @param  filePath
     * @return 单位是byte
     */
    public static long getFileSize(String filePath) {
        long size = 0;
        FileInputStream fis= null;
        FileChannel fc= null;
        try {
            File f = new File(filePath);
            if (f.exists() && f.isFile()){
                fis= new FileInputStream(f);
                fc= fis.getChannel();
                size = fc.size();
            }else{
                log.error("{}，file doesn't exist or is not a file", filePath);
            }
        } catch (FileNotFoundException e) {
            log.error("文件大小检查发生异常，{}", e.getMessage());
        } catch (IOException e) {
            log.error("文件大小检查发生异常，{}", e.getMessage());
        } finally {
            if (null!=fc){
                try{
                    fc.close();
                }catch(IOException e){
                    log.error("FileChannel资源关闭发生异常，{}", e.getMessage());
                }
            }
            if (null!=fis){
                try{
                    fis.close();
                }catch(IOException e){
                    log.error("FileInputStream资源关闭发生异常，{}", e.getMessage());
                }
            }
        }
        return size;
    }
}
